Spring boot中SmartInitializingSingleton中如何开启ManagementContext的配置

bingo 2020年09月29日 22次浏览

最近有使用springboot 内嵌容器undertow,并开启了access-log的动能,发现pattern stc=%s snt=%B dur=%D中的%D没有生效,发现是在SmartInitializingSingleton 阶段进行设置的。

SmartInitializingSingleton的定义

public interface SmartInitializingSingleton {
    void afterSingletonsInstantiated();
}

SpringApplication创建ApplicationContext时,会调用refreshContext(context);来实现BeanDefinition的初始化,和事件监听的绑定。其中会有如下的调用链条: AbstractApplicationContext#refresh() ->AbstractApplicationContext#finishBeanFactoryInitialization() ->org.springframework.beans.factory.surpoort.DefaultListableBeanFactory#preInstantiateSingletons()

    // Instantiate all remaining (non-lazy-init) singletons.
    finishBeanFactoryInitialization(beanFactory);
    // Instantiate all remaining (non-lazy-init) singletons.
    beanFactory.preInstantiateSingletons();

然后

// Trigger post-initialization callback for all applicable beans...
    for (String beanName : beanNames) {
        Object singletonInstance = getSingleton(beanName);
        if (singletonInstance instanceof SmartInitializingSingleton) {
            final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
            if (System.getSecurityManager() != null) {
                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                    smartSingleton.afterSingletonsInstantiated();
                    return null;
                }, getAccessControlContext());
            }
            else {
		// 这里开始新世界
                smartSingleton.afterSingletonsInstantiated();
            }
        }
    }

smartSingleton.afterSingletonsInstantiated();

org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration.SameManagementContextConfiguration implements SmartInitializingSingleton

org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration.DifferentManagementContextConfiguration implements SmartInitializingSingleton

以DifferentManangentContextConfiguration为例

@Override
public void afterSingletonsInstantiated() {
	if (this.applicationContext instanceof WebServerApplicationContext
			&& ((WebServerApplicationContext) this.applicationContext)
					.getWebServer() != null) {
		ConfigurableWebServerApplicationContext managementContext = this.managementContextFactory
				.createManagementContext(this.applicationContext,
						EnableChildManagementContextConfiguration.class,
						PropertyPlaceholderAutoConfiguration.class);
		managementContext.setServerNamespace("management");
		managementContext.setId(this.applicationContext.getId() + ":management");
		setClassLoaderIfPossible(managementContext);
		CloseManagementContextListener.addIfPossible(this.applicationContext,
				managementContext);
		//这里将会重新定义ApplicationContext
		managementContext.refresh();
	}
	else {
		logger.warn("Could not start embedded management container on "
				+ "different port (management endpoints are still available "
				+ "through JMX)");
	}
}

SpringBoot中关于Accesslog的配置(以内置sever为undertow为例) 在application.properteies中server.undertow.accesslog进行相关配置。 这里记录一个遇到的问题 就是accesslog.pattern中%D(响应duration)没有生效。 原因在于默认情况下,这个是不生效的。 io.undertow.server.protocol.http.HttpReadListener#handleEventWithNoRunningRequest(final ConduitStreamSourceChannel channel)

  HttpReadListener(final HttpServerConnection connection, final HttpRequestParser parser, ConnectorStatisticsImpl connectorStatistics) {
        this.connection = connection;
        this.parser = parser;
        this.connectorStatistics = connectorStatistics;
        this.maxRequestSize = connection.getUndertowOptions().get(UndertowOptions.MAX_HEADER_SIZE, UndertowOptions.DEFAULT_MAX_HEADER_SIZE);
        this.maxEntitySize = connection.getUndertowOptions().get(UndertowOptions.MAX_ENTITY_SIZE, UndertowOptions.DEFAULT_MAX_ENTITY_SIZE);
        this.recordRequestStartTime = connection.getUndertowOptions().get(UndertowOptions.RECORD_REQUEST_START_TIME, false);
        this.requireHostHeader = connection.getUndertowOptions().get(UndertowOptions.REQUIRE_HOST_HTTP11, true);
        this.allowUnknownProtocols = connection.getUndertowOptions().get(UndertowOptions.ALLOW_UNKNOWN_PROTOCOLS, false);
        int requestParseTimeout = connection.getUndertowOptions().get(UndertowOptions.REQUEST_PARSE_TIMEOUT, -1);
        int requestIdleTimeout = connection.getUndertowOptions().get(UndertowOptions.NO_REQUEST_TIMEOUT, -1);
        if(requestIdleTimeout < 0 && requestParseTimeout < 0) {
            this.parseTimeoutUpdater = null;
        } else {
            this.parseTimeoutUpdater = new ParseTimeoutUpdater(connection, requestParseTimeout, requestIdleTimeout);
            connection.addCloseListener(parseTimeoutUpdater);
        }
        state = new ParseState(connection.getUndertowOptions().get(UndertowOptions.HTTP_HEADERS_CACHE_SIZE, UndertowOptions.DEFAULT_HTTP_HEADERS_CACHE_SIZE));
    }  
   if (recordRequestStartTime) {
       Connectors.setRequestStartTime(httpServerExchange);
   }

io.undertow.attribute.ResponseTimeAttribute#readAttribute(HttpServerExchange exchange)

    @Override
    public String readAttribute(HttpServerExchange exchange) {
        long requestStartTime = exchange.getRequestStartTime();
        if(requestStartTime == -1) {
            return null;
        }
	...
    }

如何让%D生效? 方法一 使用一个BeanPostProcessor,重写public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException方法,当bean instanceof UndertowServletWebServerFactory成立时,调用factory.addBuilderCustomizers(UndertowBuilderCustomizer... customizers)

重写UndertowBuilderCustomizer的方法: builder.setServerOption(UndertowOptions.RECORD_REQUEST_START_TIME,Boolean.TRUE).

最终会在org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory#createBuilder(int port)完成调用

    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        DeploymentManager manager = createDeploymentManager(initializers);
        int port = getPort();
        Builder builder = createBuilder(port);
        return getUndertowWebServer(builder, manager, port);
    }

    private Builder createBuilder(int port) {
        Builder builder = Undertow.builder();
        if (this.bufferSize != null) {
            builder.setBufferSize(this.bufferSize);
        }
        if (this.ioThreads != null) {
            builder.setIoThreads(this.ioThreads);
        }
        if (this.workerThreads != null) {
            builder.setWorkerThreads(this.workerThreads);
        }
        if (this.directBuffers != null) {
            builder.setDirectBuffers(this.directBuffers);
        }
        if (getSsl() != null && getSsl().isEnabled()) {
            customizeSsl(builder);
        }
        else {
            builder.addHttpListener(port, getListenAddress());
        }
        for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
            customizer.customize(builder);
        }
        return builder;
    }

方法二 实现WebServerFactoryCustomizer接口,重写customize(UndertowServletWebServerFactory factory)方法

org.springframework.boot.web.server。WebServerFactoryCustomizerBeanPostProcessor#postProcessBeforeInitialization(WebServerFactory webServerFactory)最终会调用

@SuppressWarnings("unchecked")
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
    LambdaSafe
            .callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
                    webServerFactory)
            .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
            .invoke((customizer) -> customizer.customize(webServerFactory));
}

private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
    if (this.customizers == null) {
        // Look up does not include the parent context
        this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
        this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
        this.customizers = Collections.unmodifiableList(this.customizers);
    }
    return this.customizers;
}

@SuppressWarnings({ "unchecked", "rawtypes" })
private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
    return (Collection) this.beanFactory
            .getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
}

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.class

@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
        throws BeansException {

    Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        Object current = processor.postProcessBeforeInitialization(result, beanName);
        if (current == null) {
            return result;
        }
        result = current;
    }
    return result;
}

彩蛋org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextFactory


	private void registerServletWebServerFactory(ApplicationContext parent,
			AnnotationConfigServletWebServerApplicationContext childContext) {
		try {
			ConfigurableListableBeanFactory beanFactory = childContext.getBeanFactory();
			if (beanFactory instanceof BeanDefinitionRegistry) {
				BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
				registry.registerBeanDefinition("ServletWebServerFactory",
						new RootBeanDefinition(
								determineServletWebServerFactoryClass(parent)));
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore and assume auto-configuration
		}
	}

注意 这里只是把servletWebServerFactory继承过来了